﻿using mozhi.smarterp.Finance.Enums;
using Mozhi.Abp.Core.Extensions;
using Mozhi.Abp.Domain.AuthorCenter;
using Mozhi.Abp.Domain.BasicData;
using Spire.Doc.Fields;
using System;
using System.Collections.Generic;
using System.Linq;
using Volo.Abp;

namespace mozhi.smarterp.Finance.Payables
{
    /// <summary>
    /// 采购应付
    /// </summary>
    public class PurchasePayable : DocumentAggregateRoot
    {
        public DateTime DueDate { get; private set; }

        public PayableDocumentType Type { get; private set; }

        /// <summary>
        /// 立账模式
        /// </summary>
        public RecognitionModelType RecognitionModel { get; private set; }

        public Guid SupplierId { get; private set; }

        public Supplier Supplier { get; private set; }

        public Guid CurrencyId { get; private set; }

        public Currency Currency { get; private set; }

        public decimal ExchangeRate {  get; private set; }

        public bool IncludeTax {  get; private set; }

        /// <summary>
        /// 采购部门
        /// </summary>
        public Guid? PurchaserDepartmentId { get; private set; }

        public Department PurchaserDepartment {  get; private set; }

        /// <summary>
        /// 采购员
        /// </summary>
        public Guid? PurchaserId { get; private set; }

        public Employee Purchaser { get; private set; }

        public Guid? PaymentTermId { get; private set; }

        public decimal Amount { private set; get; }

        public decimal AmountFr { private set; get; }

        public decimal TotalAmount { private set; get; }

        public decimal TotalAmountFr { private set; get; }

        public bool IsCreditNote {  get; private set; }

        public List<PurchasePayableItem> Items { get; private set; }

        private PurchasePayable() { }

        public PurchasePayable(Guid id , string number , DateTime date , DateTime dueDate , Supplier supplier , Currency currency , PayableDocumentType type, RecognitionModelType recognitionModel, 
            bool includeTax , Department purchaserDepartment ,Employee purchaser , Guid? paymentTermId , bool isCreditNote , string comment)
        {
            Id = id;
            Number = number;
            Date = date;
            DueDate = dueDate;
            Currency = currency;
            CurrencyId = currency==null ? default : currency.Id;
            ExchangeRate = currency==null ? default : currency.ExchangeRate;

            IncludeTax = includeTax;
            PurchaserDepartmentId = purchaserDepartment?.Id;
            PurchaserId = purchaser?.Id;
            PaymentTermId = paymentTermId;
            Type = type;
            RecognitionModel = recognitionModel;

            Supplier = supplier;

            SupplierId = supplier==null ? default : supplier.Id;

            IsCreditNote = isCreditNote;

            Comment = comment;

            Items = new List<PurchasePayableItem>();

            Validate();
        }

        public void Change(DateTime date, DateTime dueDate, Supplier supplier, Currency currency, PayableDocumentType type, RecognitionModelType recognitionModel,
            bool includeTax, Department purchaserDepartment, Employee purchaser, Guid? paymentTermId, string comment)
        {
            Date = date;
            DueDate = dueDate;
            Currency = currency;
            CurrencyId = currency == null ? default : currency.Id;
            ExchangeRate = currency == null ? default : currency.ExchangeRate;

            IncludeTax = includeTax;
            PurchaserDepartmentId = purchaserDepartment?.Id;
            PurchaserId = purchaser?.Id;
            PaymentTermId = paymentTermId;
            Type = type;
            RecognitionModel = recognitionModel;

            Supplier = supplier;

            SupplierId = supplier == null ? default : supplier.Id;

            Comment = comment;

            Items = Items ?? new List<PurchasePayableItem>();

            Validate();
        }

        public void SetItems(List<PurchasePayableItem> items)
        {
            foreach (var item in items)
            {
                AddOrUpdateItem(item);
            }

            var ids = items.Select(x => x.Id).ToList();

            var removedItems = Items.Where(x => !ids.Contains(x.Id)).ToList();

            foreach (var item in removedItems)
            {
                Items.Remove(item);
            }

            CalculateAmount();
        }


        #region
        private void Validate()
        {
            Check.NotNullOrEmpty(Number, nameof(Number));
            Check.NotDefaultOrNull<Guid>(CurrencyId, nameof(CurrencyId));
            Check.NotDefaultOrNull<Guid>(SupplierId, nameof(SupplierId));
            Check.NotDefaultOrNull<decimal>(ExchangeRate, nameof(ExchangeRate));
            Check.NotDefaultOrNull<PayableDocumentType>(Type, nameof(Type));
            Check.NotDefaultOrNull<RecognitionModelType>(RecognitionModel, nameof(RecognitionModel));

        }

        private void CalculateAmount()
        {
            foreach (var item in Items)
            {
                item.CalculateAmount(ExchangeRate);
            }

            Amount = Items.Sum(x => x.Amount);
            AmountFr = Items.Sum(x => x.AmountFr);

            TotalAmount = Items.Sum(x => x.TotalAmount);
            TotalAmountFr = Items.Sum(x => x.TotalAmountFr);
        }

        private void AddOrUpdateItem(PurchasePayableItem item)
        {
            ValidateStatus();

            if (Type == PayableDocumentType.Standard)
            {
                //需要产品，并且费用项目不能填
                if (item.ExpenseItemId.HasValue)
                    throw new UserFriendlyException("标准应付不能填写费用项目", "100000");

                if (!item.ProductId.HasValue || item.Product == null)
                    throw new UserFriendlyException("标准应付产品不能为空", "100000");

                if (item.Product.Type != (int)ProductType.Stock)
                    throw new UserFriendlyException($"标准应付产品:{item.Product.Code}-{item.Product.Name}类型不是库存", "100000");

                if (!item.Product.Purchasable)
                    throw new UserFriendlyException($"标准应付产品:{item.Product.Code}-{item.Product.Name}不可采购", "100000");

                item.SetExpenseBurdenDepartment(null);
            }
            else if (Type == PayableDocumentType.Expense)
            {
                if (item.ProductId.HasValue)
                    throw new UserFriendlyException("费用应付不能填写产品", "100000");

                if ((item.IsRecordedCost == null || item.IsRecordedCost == false) && !item.ExpenseBurdenDepartmentId.HasValue)
                    throw new UserFriendlyException("不计入费用的明细必须选择费用承担部门", "100000");
            }
            else
            {
                //资产应付
                if (item.ExpenseItemId.HasValue)
                    throw new UserFriendlyException("资产应付不能填写费用项目", "100000");

                if (!item.ProductId.HasValue || item.Product == null)
                    throw new UserFriendlyException("资产应付产品不能为空", "100000");

                if (item.Product.Type != (int)ProductType.Asset)
                    throw new UserFriendlyException($"资产应付产品:{item.Product.Code}-{item.Product.Name}类型不是资产", "100000");

                if (!item.Product.Assetable)
                    throw new UserFriendlyException($"标准应付产品:{item.Product.Code}-{item.Product.Name}不可作为资产", "100000");

                item.SetExpenseBurdenDepartment(null);
            }

            var match = Items.FirstOrDefault(x => x.Id == item.Id);

            if (match == null)
            {
                match = item;

                match.CalculateAmount(ExchangeRate);

                Items.Add(match);
            }
            else
            {
                match.Change(item.Product, item.ExpenseItem, item.Price, item.TaxRate, item.BasicQuantity, item.AmountFr, item.ExpenseBurdenDepartment, item.Remark);

                item.CalculateAmount(ExchangeRate);
            }
        }

        #endregion
    }

    public class PurchasePayableItem : DocumentEntity
    {
        public Guid? ProductId { get; private set; }

        public Product Product { get; private set; }

        public Guid? ExpenseItemId { get; private set; }

        public ExpenseItem ExpenseItem { get; private set; }

        /// <summary>
        /// 计价单位
        /// </summary>
        public Guid BasicUnitId { get; private set; }

        /// <summary>
        /// 计价数量
        /// </summary>
        public decimal BasicQuantity {  get; private set; }

        public decimal Price { get; private set; }

        public decimal PriceIncludeTax { get; private set; }

        public decimal PriceExcludeTax { get; private set; }

        public Guid? TaxRateId { get; private set; }

        public TaxRate TaxRate { get; private set; }

        public decimal? TaxRateValue { get; private set; }

        public decimal TaxAmountFr {  get; private set; }

        public decimal TaxAmount { get; private set; }

        public decimal TotalAmountFr {  get; private set; }

        public decimal TotalAmount { get; private set;}

        /// <summary>
        /// 是否计入采购成本
        /// </summary>
        public bool? IsRecordedCost { get; private set; }

        /// <summary>
        /// 费用承担部门
        /// </summary>
        public Guid? ExpenseBurdenDepartmentId {  get; private set; }

        public Department ExpenseBurdenDepartment {  get; private set; }

        private PurchasePayableItem() { }

        public PurchasePayableItem(Guid id, Product product, ExpenseItem expenseItem, decimal price, TaxRate taxRate, decimal basicQuantity, decimal? amountFr , Department expenseBurdenDepartment, string remark)
        {
            Id = id;

            Product = product;
            ProductId = product == null ? default : product.Id;

            ExpenseItem = expenseItem;
            ExpenseItemId = expenseItem == null ? default : expenseItem.Id;

            BasicUnitId = product == null ? default : product.BasicUnitId;
            BasicQuantity = basicQuantity;
            Price = price;

            TaxRate = taxRate;
            TaxRateId = taxRate?.Id;
            TaxRateValue = taxRate?.Rate;

            AmountFr = amountFr.HasValue ? amountFr.Value : default;

            SetExpenseBurdenDepartment(expenseBurdenDepartment);

            Remark = remark;

            Validate();
        }

        public void Change(Product product, ExpenseItem expenseItem, decimal price, TaxRate taxRate, decimal basicQuantity, decimal? amountFr, Department expenseBurdenDepartment, string remark)
        {
            Product = product;
            ProductId = product == null ? default : product.Id;

            ExpenseItem = expenseItem;
            ExpenseItemId = expenseItem == null ? default : expenseItem.Id;

            BasicUnitId = product == null ? default : product.BasicUnitId;
            BasicQuantity = basicQuantity;
            Price = price;

            TaxRate = taxRate;
            TaxRateId = taxRate?.Id;
            TaxRateValue = taxRate?.Rate;

            AmountFr = amountFr.HasValue ? amountFr.Value : default;

            SetExpenseBurdenDepartment(expenseBurdenDepartment);

            Remark = remark;

            Validate();
        }

        public void SetExpenseBurdenDepartment(Department department)
        {
            ExpenseBurdenDepartment = department;

            ExpenseBurdenDepartmentId = department?.Id;
        }

        public void CalculateAmount(decimal exchangeRate)
        {
            if (ProductId.HasValue)
            {
                PriceIncludeTax = TaxRate == null ? Price : TaxRate.GetIncludeTaxValue(Price, false);
                PriceExcludeTax = TaxRate == null ? Price : TaxRate.GetExcludeTaxValue(Price, false);

                AmountFr = (BasicQuantity * PriceExcludeTax).ToMoney();
                Amount = (AmountFr * exchangeRate).ToMoney();

                TotalAmountFr = (BasicQuantity * PriceIncludeTax).ToMoney();
                TotalAmount = (TotalAmountFr * exchangeRate).ToMoney();

                TaxAmountFr = TotalAmountFr - AmountFr;
                TaxAmount = TotalAmount - Amount;
            }
            else
            {
                Amount = (AmountFr * exchangeRate).ToMoney();
                TotalAmountFr = AmountFr;
                TotalAmount = Amount;

                TaxAmountFr = TotalAmountFr - AmountFr;
                TaxAmount = TotalAmount - Amount;
            }
        }

        #region
        private void Validate()
        {
            if (ProductId.HasValue)
            {
                Check.NotDefaultOrNull<Guid>(BasicUnitId, nameof(BasicUnitId));

                Check.NotDefaultOrNull<decimal>(BasicQuantity, nameof(BasicQuantity));
                Check.NotDefaultOrNull<decimal>(Price, nameof(Price));

                if (Price <= 0)
                    throw new UserFriendlyException("单价必须大于0", "100000");

                if (BasicQuantity <= 0)
                    throw new UserFriendlyException("数量必须大于0", "100000");

                if(Product==null)
                    throw new UserFriendlyException($"产品：{ProductId}不存在", "100000");

                Product.Validate();
            }

            if (ExpenseItemId.HasValue)
            {
                if (AmountFr == 0)
                    throw new UserFriendlyException("不含税金额必须大于0", "100000");

                if (!ExpenseBurdenDepartmentId.HasValue)
                    throw new UserFriendlyException("费用承担部门不能为空");
            }
        }
        #endregion
    }
}
